﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using KamLib;
using KamLib.Linq.Expressions;


namespace Ext.Net.UIS.Script
{

    public class TermScript
    {
        #region Statics

        public static readonly ParameterExpression Field = Expression.Parameter(typeof(object), "field");
        public static readonly ParameterExpression Record = Expression.Parameter(typeof(object), "record");
        public static readonly ParameterExpression Value = Expression.Parameter(typeof(object), "value");

        [TermMethod]
        public static ScriptTerm Term(string name) { return new ScriptTerm(name); }

        public static ConstantExpression Raw(string javaScript) { return JsExpressionVisitor.Raw(javaScript); }


        #region If

        public static ConditionalExpression If(Expression<Func<bool>> test, Expression[] then, Expression[] @else = null)
        {
            return Expression.Condition(test.Body,
                then == null ? Raw("{}") : then.Length > 1 ? Expression.Block(typeof(void), then) : then[0],
                @else == null ? Raw("{}") : @else.Length > 1 ? Expression.Block(typeof(void), @else) : @else[0]
            );
        }

        public static ConditionalExpression If(Expression<Func<bool>> test, Expression then, Expression[] @else = null)
        {
            return Expression.Condition(test.Body,
                then ?? Raw("{}"),
                @else == null ? Raw("{}") : @else.Length > 1 ? Expression.Block(typeof(void), @else) : @else[0]
            );
        }

        public static ConditionalExpression If(Expression<Func<bool>> test, Expression[] then, Expression @else)
        {
            return Expression.Condition(test.Body,
                then == null ? Raw("{}") : then.Length > 1 ? Expression.Block(typeof(void), then) : then[0],
                @else ?? Raw("{}")
            );
        }

        public static ConditionalExpression If(string term, Expression[] then, Expression[] @else = null) { return If(() => Term(term), then, @else); }
        public static ConditionalExpression If(string term, Expression then, Expression[] @else = null) { return If(() => Term(term), then, @else); }
        public static ConditionalExpression If(string term, Expression[] then, Expression @else) { return If(() => Term(term), then, @else); }

        #endregion

        public static readonly MethodInfo TermInfo = typeof(TermScript).GetMethod("Term");

        #endregion


        public TermScript(Expression expression)
        {
            Expression = expression;
        }

        public Expression Expression { get; set; }

        public TermNode TermNode;
        public ModelField ModelField;

        public virtual string ToScript(TermNode termNode, ModelField modelField = null)
        {
            if (Expression == null) return null;
            var visitor = NewVisitor();
            TermNode = termNode;
            ModelField = modelField;
            visitor.Context = this;
            visitor.Visit(Expression);
            return visitor.ToString();
        }

        protected virtual JsExpressionVisitor NewVisitor() { return new JsExpressionVisitor(); }


        #region Attributes

        public class ScriptTermConstantAttribute : JsExpressionVisitor.CustomVisitConstantAttribute
        {
            public override bool Append(JsExpressionVisitor v, ConstantExpression node)
            {
                var term = node.Value as ScriptTerm;
                // ReSharper disable RedundantCast.0
                if ((object)term == null) return false;
                // ReSharper restore RedundantCast.0

                var termScript = v.Context as TermScript;
                if (termScript == null) return false;

                var termNode = termScript.TermNode.FindNearest(a => a.Term != null && a.Term.Name == term.Name);
                if (termNode == null)
                    throw new Exception(
                        "ScriptTermConstantAttribute.Append: " +
                        "can't find TermNode by name of term '" + term.Name + "' " +
                        "in TermNode '" + termScript.TermNode + "'"
                    );

                v.Append("Ext.uisGet('");
                v.Append(termNode.ID);
                v.Append("')");



                return true;
            }
        }

        public class FieldPropertyAttribute : JsExpressionVisitor.CustomVisitMemberAttribute
        {
            public FieldPropertyAttribute(string fieldName, string methodName)
            {
                FieldName = fieldName;
                MethodName = methodName;
            }

            public string FieldName { get; set; }
            public string MethodName { get; set; }

            public override bool AssignAsCall(JsExpressionVisitor v, MemberExpression node) { return true; }

            public override string GetMemberName(JsExpressionVisitor v, MemberExpression node, bool useCall) { return useCall ? MethodName : FieldName; }
        }

        public class TermMethodAttribute : JsExpressionVisitor.CustomVisitMethodAttribute
        {
            public override bool Append(JsExpressionVisitor v, MethodCallExpression node)
            {
                if (node.Method != TermInfo)
                    return base.Append(v, node);

                var name = JsExpressionVisitor.GetValue(node.Arguments[0]) as string;
                var termScript = (TermScript)v.Context;

                if (termScript.ModelField == null)
                {
                    var termNode = termScript.TermNode.FindNearest(a => a.Term != null && a.Term.Name == name);
                    if (termNode == null)
                        throw new Exception(
                            "ScriptTermConstantAttribute.Append: " +
                            "can't find TermNode by name of term '" + name + "' " +
                            "in TermNode '" + termScript.TermNode + "'"
                        );
                    v.Append("Ext.uisGet('");
                    v.Append(termNode.ID);
                    v.Append("').getValue()");
                }
                else
                {
                    v.Append("record.data.");
                    v.Append(name);
                    termScript.ModelField.References.Register(name);
                }

                return true;
            }
        }

        #endregion


    }
}
